#ifndef SG_SPECKWRAPPER_HPP_INCLUDED
#define SG_SPECKWRAPPER_HPP_INCLUDED

#include <algorithm>
#include "blockcipher.hpp"
#include "speck.hpp"

template<int N, int M>
class speck_blockcipher : public blockcipher
{
public:
	static const int max_rounds = speck::params_for_N<N>::round_base+M;
	typedef typename speck::block_t<N>::value_type value_type;

	speck_blockcipher() : num_rounds(max_rounds) {}

	int  rounds() const {return num_rounds;}
	void rounds(int newvalue) {num_rounds = std::min(newvalue,max_rounds);}

	int block_bytes() const override {return N/4;}
	int key_bytes()   const override {return N*M/8;}
	void keysetup(const unsigned char key[]) override;
	void encrypt(const unsigned char in[], unsigned char out[]) const override;
	void decrypt(const unsigned char in[], unsigned char out[]) const override;
	void identify(std::ostream& out) const override;

private:
	speck::subkeys_t<N,max_rounds> subkeys;
	int num_rounds;
};

/*
 * The authors of SPECK did not specify an encoding for the key (key
 * words k[0], k[1], ..., k[M-1]) nor an encoding for the block (words
 * x and y). I simply use the little endian byte order for the words
 * as well as a least-to-most significant word order. The significance
 * of the words is taken from the authors' test vector listings. Here
 * is an example for Speck-64/96 where N=32 and M=3:
 *
 *                     k2             k1             k0
 *   Key       :  13 12 11 10    0b 0a 09 08    03 02 01 00  (96 bits)
 *
 *                                    x              y
 *   Plaintext :                 74 61 46 20    73 6e 61 65  (64 bits)
 *   Ciphertext:                 9f 79 52 ec    41 75 94 6c  (64 bits)
 *
 *                11 10  9  8     7  6  5  4     3  2  1  0
 *                <------------ byte ordering -------------
 *
 * So, I actually place y before x in the byte arrays.
 */

namespace speck_bc_details {

template<int N>
void bytes2words(int nwords,
	const unsigned char in[],
	typename speck::block_t<N>::value_type out[])
{
	typedef typename speck::block_t<N>::value_type value_type;
	for (int i=0; i<nwords; ++i) {
		value_type tmp = 0;
		for (int b=0; b<N; b+=8) {
			tmp |= static_cast<value_type>(*in++ & 0xFF) << b;
		}
		*out++ = tmp;
	}
}

template<int N>
void words2bytes(int nwords,
	const typename speck::block_t<N>::value_type in[],
	unsigned char out[])
{
	typedef typename speck::block_t<N>::value_type value_type;
	for (int i=0; i<nwords; ++i) {
		value_type tmp = *in++;
		for (int b=0; b<N; b+=8) {
			*out++ = (tmp >> b) & 0xFF;
		}
	}
}

} // namespace speck_bc_details

template<int N, int M>
void speck_blockcipher<N,M>::keysetup(const unsigned char key[])
{
	speck::key_t<N,M> k;
	speck_bc_details::bytes2words<N>(M,key,k.words);
	subkeys = key_expand(k);
}

template<int N, int M>
void speck_blockcipher<N,M>::encrypt(const unsigned char in[], unsigned char out[]) const
{
	speck::block_t<N> blk;
	speck_bc_details::bytes2words<N>(1,in+ 0 ,&blk.y);
	speck_bc_details::bytes2words<N>(1,in+N/8,&blk.x);
	speck::encrypt(blk,subkeys.begin(),subkeys.begin()+num_rounds);
	speck_bc_details::words2bytes<N>(1,&blk.y,out+ 0 );
	speck_bc_details::words2bytes<N>(1,&blk.x,out+N/8);
}

template<int N, int M>
void speck_blockcipher<N,M>::decrypt(const unsigned char in[], unsigned char out[]) const
{
	speck::block_t<N> blk;
	speck_bc_details::bytes2words<N>(1,in+ 0 ,&blk.y);
	speck_bc_details::bytes2words<N>(1,in+N/8,&blk.x);
	speck::decrypt(blk,subkeys.begin(),subkeys.begin()+num_rounds);
	speck_bc_details::words2bytes<N>(1,&blk.y,out+ 0 );
	speck_bc_details::words2bytes<N>(1,&blk.x,out+N/8);
}

template<int N, int M>
void speck_blockcipher<N,M>::identify(std::ostream& out) const
{
	out << "Speck" << (2*N) << '/' << (M*N) << "[R=" << num_rounds << ']';
}

#endif
